package org.JWCB.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.JWCB.common.file.service.OSSService;
import org.JWCB.system.domain.exam.dto.ExamAddDto;
import org.JWCB.system.domain.exam.dto.ExamEditDto;
import org.JWCB.system.domain.exam.dto.ExamQueryDto;
import org.JWCB.system.domain.exam.dto.ExamQuestionAddDto;
import org.JWCB.system.domain.exam.entity.Exam;
import org.JWCB.system.domain.exam.entity.ExamQuestion;
import org.JWCB.system.domain.exam.vo.ExamDetailVo;
import org.JWCB.system.domain.exam.vo.ExamListItemVo;
import org.JWCB.system.domain.question.entity.Question;
import org.JWCB.system.domain.question.vo.QuestionListItemVo;
import org.JWCB.common.core.domain.vo.R;
import org.JWCB.common.core.enums.ExamStatus;
import org.JWCB.common.core.enums.ResultCode;
import org.JWCB.common.core.exception.ServiceException;
import org.JWCB.system.manager.ExamCacheManager;
import org.JWCB.system.mapper.exam.ExamMapper;
import org.JWCB.system.mapper.exam.ExamQuestionMapper;
import org.JWCB.system.mapper.question.QuestionMapper;
import org.JWCB.system.service.exam.IExamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper,ExamQuestion> implements IExamService {

    @Autowired
    private ExamMapper examMapper;
    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private ExamCacheManager examCacheManager;

    @Autowired
    private OSSService ossService;


    @Value("${file.oss.questionTmpDir}")
    private String questionTmpDir;

    @Value("${file.oss.questionFinallyDir}")
    private String questionFinallyDir;

    private static final Pattern IMG_PATTERN = Pattern.compile("<img[^>]*src=\"([^\"]*)\"[^>]*>");


    @Override
    public List<ExamListItemVo> list(ExamQueryDto examQueryDto) {
        PageHelper.startPage(examQueryDto.getPageNum(),examQueryDto.getPageSize());
        return examMapper.selectExamList(examQueryDto);
    }

    @Override
    public R<String> add(ExamAddDto examAddDto) {
        checkExamSave(examAddDto,null);
        Exam exam = new Exam();
        BeanUtil.copyProperties(examAddDto,exam);
        if(examMapper.insert(exam) <= 0) {
            return R.fail();
        }
        return R.ok(exam.getExamId().toString());
    }



    @Override
    public int del(Long examId) {
        Exam exam = getExam(examId);
        checkExamPublish(exam);
        checkExamStarted(exam);
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        return examMapper.deleteById(examId);
    }

    @Override
    @Transactional
    public int publish(Long examId) {
        Exam exam = getExam(examId);
        checkExamEnded(exam);
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        if(count == null || count <= 0) {
            throw new ServiceException(ResultCode.EXAM_EMPTY_QUESTION);
        }
        exam.setStatus(ExamStatus.PUBLISHED.getCode());
        int res =  examMapper.updateById(exam);
        examCacheManager.addCache(exam);
        return res;
    }

    @Override
    public int unpublish(Long examId) {
        Exam exam = getExam(examId);
        checkExamStarted(exam);
        checkExamEnded(exam);
        exam.setStatus(ExamStatus.UN_PUBLISHED.getCode());
        int res = examMapper.updateById(exam);
        examCacheManager.delCache(exam);

        return res;
    }


    @Override
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public boolean addQuestion(ExamQuestionAddDto examQuestionAddDto) {
        Exam exam = getExam(examQuestionAddDto.getExamId());
        checkExamStarted(exam);
        checkExamPublish(exam);

        Set<Long> questionIdSet = examQuestionAddDto.getQuestionIdSet();
        if(CollectionUtil.isEmpty(questionIdSet)) {
            return true;
        }
        List<ExamQuestion> oldQuestions = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>().select(ExamQuestion::getQuestionId).eq(ExamQuestion::getExamId,exam.getExamId()));
        List<Question> questionList = questionMapper.selectBatchIds(questionIdSet);
        if(CollectionUtil.isEmpty(questionList) || questionList.size() < questionIdSet.size()) {
            throw new ServiceException(ResultCode.QUESTION_NOT_EXIST);
        }
        for(ExamQuestion examQuestion : oldQuestions) {
            if(questionIdSet.contains(examQuestion.getQuestionId())) {
                throw new ServiceException(ResultCode.QUESTION_ALREADY_EXIST);
            }
        }
        return saveExamQuestion(questionIdSet,exam,oldQuestions.size());
    }


    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean saveExamQuestion(Set<Long> questionIdSet, Exam exam,Integer oldQuestionSize) {
        int num = oldQuestionSize + 1;
        List<ExamQuestion> examQuestions = new ArrayList<>();
        for(Long questionId : questionIdSet) {
            ExamQuestion question = new ExamQuestion();
            question.setExamId(exam.getExamId());
            question.setQuestionId(questionId);
            question.setQuestionOrder(num++);
            examQuestions.add(question);
        }
        return saveBatch(examQuestions);
    }

    @Override
    @Transactional
    public boolean updateQuestionOrder(ExamQuestionAddDto examQuestionAddDto) {
        Exam exam = getExam(examQuestionAddDto.getExamId());
        checkExamStarted(exam);
        checkExamPublish(exam);
        List<ExamQuestion> oldQuestions = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examQuestionAddDto.getExamId()));
        Set<Long> newQuestionIdSet = examQuestionAddDto.getQuestionIdSet();
        if(oldQuestions.size() < newQuestionIdSet.size()) {
            throw new ServiceException(ResultCode.EXAM_QUESTION_NUM_NOT_MATCH);
        }
        Map<Long,Integer> currentOrderMap = new HashMap<>();
        Map<Long,ExamQuestion> questionIdMap = new HashMap<>();
        for(ExamQuestion examQuestion : oldQuestions) {
            currentOrderMap.put(examQuestion.getQuestionId(),examQuestion.getQuestionOrder());
            questionIdMap.put(examQuestion.getQuestionId(),examQuestion);
        }
        int newOrder = 1;
        List<ExamQuestion> updatedQuestions = new ArrayList<>();
        for(Long questionId : newQuestionIdSet) {
            Integer currentOrder = currentOrderMap.get(questionId);
            if(currentOrder == null) {
                throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
            }
            if(currentOrder != newOrder) {
                ExamQuestion question = questionIdMap.get(questionId);
                question.setQuestionOrder(newOrder);
                updatedQuestions.add(question);
            }
            newOrder++;
        }
        if(CollectionUtil.isEmpty(updatedQuestions)) {
            return true;
        }
        return updateBatchById(updatedQuestions);
    }




    @Override
    public ExamDetailVo examDetail(Long examId) {
        Exam exam = getExam(examId);
        ExamDetailVo examDetailVo = new ExamDetailVo();
        examDetailVo.setTitle(exam.getTitle());
        examDetailVo.setStartTime(exam.getStartTime());
        examDetailVo.setEndTime(exam.getEndTime());
        List<QuestionListItemVo> questionListItemVos = examQuestionMapper.selectExamQuestionList(examId);
        examDetailVo.setQuestionSet(questionListItemVos);
        return examDetailVo;
    }


    @Override
    public int edit(ExamEditDto examEditDto) {
        Exam exam = getExam(examEditDto.getExamId());
        checkExamPublish(exam);
        if(ExamStatus.PUBLISHED.getCode().equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_ALREADY_PUBLISH);
        }
        checkExamSave(examEditDto,examEditDto.getExamId());
        exam.setTitle(examEditDto.getTitle());
        exam.setStartTime(examEditDto.getStartTime());
        exam.setEndTime(examEditDto.getEndTime());
        return examMapper.updateById(exam);
    }


    @Override
    public int delQuestion(Long examId, Long questionId) {
        Exam exam = getExam(examId);
        checkExamPublish(exam);
        checkExamStarted(exam);
        return examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId,examId).eq(ExamQuestion::getQuestionId,questionId));
    }




    private void checkExamSave(ExamAddDto examSaveDto,Long examId){
        if(examSaveDto.getEndTime().isBefore(examSaveDto.getStartTime())) {
            throw new ServiceException(ResultCode.EXAM_TIME_ERROR);
        }
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>().select(Exam::getExamId).eq(Exam::getTitle, examSaveDto.getTitle()).ne(examId != null,Exam::getExamId,examId));
        if(CollectionUtil.isNotEmpty(examList)) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }
    }



    /**
     * 竞赛是否存在?
     * @return 存在 or 不存在
     */
    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if(exam == null) {
            throw  new ServiceException(ResultCode.EXAM_NOT_EXIST);
        }
        return exam;
    }


    /**
     * 根据竞赛id得到竞赛详情
     * @param examId 竞赛id
     * @return 竞赛详情
     */

    /**
     * 判定竞赛是否已经开始
     * @param exam 竞赛信息
     */
    private void checkExamStarted(Exam exam) {
        if(exam.getStartTime().isBefore(LocalDateTime.now())) {
            // 竞赛已经开始
            throw new ServiceException(ResultCode.EXAM_HAVE_STARTED);
        }
    }


    /**
     * 判定竞赛是否已经结束
     * @param exam 竞赛信息
     */
    private void checkExamEnded(Exam exam) {
        if(exam.getEndTime().isBefore(LocalDateTime.now())) {
            // 竞赛已经结束
            throw new ServiceException(ResultCode.EXAM_HAVE_END);
        }
    }



    /**
     * 判定竞赛是不是已经发布
     * @param exam 竞赛
     */
    private void checkExamPublish(Exam exam) {
        if(ExamStatus.PUBLISHED.getCode().equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_ALREADY_PUBLISH);
        }
    }




    /**
     * 将临时文件转储到正式目录
     * @param questionDesc 课程描述
     * @return 处理后的课程描述
     */
    private String transferTempFiles(String questionDesc) {
        if (StrUtil.isEmpty(questionDesc)) {
            return questionDesc;
        }
        // 处理图片文件
        Matcher imgMatcher = IMG_PATTERN.matcher(questionDesc);
        while (imgMatcher.find()) {
            String tempUrl = imgMatcher.group(1);
            String finalUrl = transferFile(tempUrl);
            questionDesc = questionDesc.replace(tempUrl, finalUrl);
        }

        return questionDesc;
    }

    /**
     * 将单个文件从临时目录转储到正式目录
     * @param tempUrl 临时文件URL
     * @return 正式文件URL
     */
    private String transferFile(String tempUrl) {
        try {
            // 从URL中提取文件路径
            String tempPath = tempUrl.substring(tempUrl.indexOf(questionTmpDir));
            String fileName = tempPath.substring(tempPath.lastIndexOf("/") + 1);

            // 构建源路径和目标路径
            String targetKey = questionFinallyDir +  fileName;
            ossService.transferFile(tempPath,targetKey);
            return tempUrl.replace(questionTmpDir, questionFinallyDir);
        } catch (Exception e) {
            log.error("文件转储失败: {}", tempUrl, e);
            throw new ServiceException(ResultCode.FAILED_FILE_UPLOAD);
        }
    }
}
